点击标题下「异步图书」可快速关注
用Python实现“猜数字”游戏
在本文中,我们准备编写一个叫做“猜数字”的游戏。计算机想到一个1到20之间的随机数,让你来猜它是几。计算机会告诉你每次猜的数太大还是太小。如果你能够在6次之内猜到正确的数字,就赢了。
这是一个进行编程练习的很好的游戏,因为在这个小程序中,用到了随机数字、循环和用户输入。我们已经介绍过如何把值转换成不同的数据类型,以及为什么需要这么做。因为这个程序是一个游戏,所以我们会把其用户称为玩家。
主要内容:
import语句;
模块;
randint()函数;
for语句;
语句块;
str()函数、int()函数和float()函数;
布尔类型;
比较操作符;
=和==的区别;
if语句;
break语句。
3.1 “猜数字”的运行示例
如下是玩家运行程序时的样子。玩家输入的文本用粗体表示。
Hello!
What is your name?
AlbertWell, Albert, I am thinking of a number between 1 and 20.
Take a guess.10
Your guess is too high.
Take a guess.
2
Your guess is too low.Take a guess.
4Good job, Albert! You guessed my number in 3 guesses!
3.2 “猜数字”程序的源代码
通过点击File►New Window打开一个新的文件编辑器窗口。在出现的空白窗口中,输入源代码,并且把它保存为guess.py。然后按下F5键来运行程序。当在文件编辑器中输入代码时,要留意一些代码行前面的空格。有些行有4个或者8个缩进空格。
如果输入这些代码之后出现错误,请使用在http//www.nostarch.com/inventwithpython #diff的在线diff工具,对比你输入的代码和本书代码。
guess.py 1. # This is a Guess the Number game.
2. import random 3.
4. guessesTaken = 0
5.
6. print('Hello! What is your name?')
7. myName = input()
8.
9. number = random.randint(1, 20)
10. print('Well, ' + myName + ', I am thinking of a number between 1 and 20.')
11.
12. for i in range(6):
13. print('Take a guess.') # Four spaces in front of "print"
14. guess = input()
15. guess = int(guess)
16.
17. if guess < number:
18. print('Your guess is too low.') # Eight spaces in front of "print"
19.
20. if guess > number:
21. print('Your guess is too high.')
22.
23. if guess == number:
24. break
25.
26. if guess == number:
27. guessesTaken = str(guessesTaken)
28. print('Good job, ' + myName + '! You guessed my number in ' +
guessesTaken + ' guesses!')
29.
30. if guess != number:
31. number = str(number)
32. print('Nope. The number I was thinking of was ' + number + '.')
3.3 导入random模块
我们来看一下这个程序的前两行代码:
1. # This is a Guess the Number game.2. import random
第1行是一条注释,我们在第2章中介绍过注释。记住,Python会忽略掉#字符后边的所有内容。注释只是提醒我们程序要做什么。
第2行是一条import语句。记住,语句是执行某些动作的指令,而不像表达式那样会计算为一个值。我们已经见过语句了,例如把一个值存储到一个变量中的赋值语句。
Python包含许多内建的函数,有些函数存在于叫做模块的单独的程序中。可以使用一条import语句把模块导入到你的程序中,这样就可以使用这些函数了。
第2行导入了名为random的模块,以便程序可以调用random.randint()函数。这个函数将产生一个随机数字,供用户进行猜测。
既然已经导入了random模块,我们需要设置一些变量来存储程序稍后将要用到的值。
第4行创建了一个名为guessesTaken的新的变量:
4. guessesTaken = 0
我们将把玩家猜过的次数保存到这个变量中。因为此时玩家还没有做过任何猜测,所以这里保存的是整数0。
6. print('Hello! What is your name?')7. myName = input()
第6行和第7行与我们在第2章的Hello World程序中见到的代码行一样。程序员经常复用其他程序中的代码,以减少自己的工作量。
第6行是对print()函数的一次调用。请记住,函数就像是程序中的一个小程序。当程序调用一个函数时,它会运行这个小程序。print()函数中的代码把传递给它的字符串参数显示到屏幕上。
第7行要求用户输入姓名,并且将输入值存储到myName变量中(记住,这个字符串可能并不是玩家的真实姓名。它可能只是玩家输入的任意字符串。计算机则只会无条件地执行指令)。
3.4 用random.randint()函数生成随机数
既然已经设置好了其他的变量,我们可以使用random模块的函数来设置计算机的神秘数字了:
9. number = random.randint(1, 20)
第9行调用了一个名为randint()的新函数,并且把返回值存储到了变量number中。记住,函数调用可以是表达式的一部分,因为函数调用也会求得一个值。
randint()函数是由random模块提供的,所以在它前边要加上random.(别漏掉那个点),这用来告诉Python,randint()是random模块中的函数。
randint()函数会返回一个随机的整数,该整数在接收到的两个整数参数之间(也包括这两个整数)。第9行把1和20传入到函数名称后边的圆括号中,两个数之间用逗号隔开。把randint()返回的随机整数存储到名为number的变量中,这就是玩家试图猜测的神秘数字。
稍等片刻,回到交互式shell,并且输入import random,以导入random模块。然后输入random.randint(1, 20),以查看这个函数调用的结果。例如,在交互式shell中输入下面的语句。它会返回1到20之间的一个整数。再输一次这行代码,函数调用会返回一个不同的整数。randint()函数每次返回一个随机的整数,就像每次掷色子都会得到一个随机数字一样。当调用randint()函数时,所得到的结果可能是不同的(毕竟它是随机的)。
>>> import random>>> random.randint(1, 20)12>>> random.randint(1, 20)18>>> random.randint(1, 20)3>>> random.randint(1, 20)18>>> random.randint(1, 20)7
也可以通过修改参数,来尝试不同范围的数字。例如,输入random.randint(1, 4),只会得到1到4之间的整数(包含1和4)。或者尝试random.randint(1000, 2000),来获取1000到2000之间的整数。
在交互式shell中输入如下这行代码,看看会得到什么数字:
>>> random.randint(1, 4)3>>> random.randint(1000, 2000)1294
对游戏代码稍作修改,就可以使得游戏的行为有所不同。在我们的初始代码中,使用了1到20之间的一个整数:
9. number = random.randint(1, 20)10. print('Well, ' + myName + ', I am thinking of a number between 1 and 20.')
将其修改为如下所示:
9. number = random.randint(1, 100)10. print('Well, ' + myName + ', I am thinking of a number between 1 and 100.')
现在计算机将会考虑1到100之间的一个整数,而不是1到20之间的一个整数。第9行的改动将会改变随机数字的范围,但是要记住还要修改第10行,以便让游戏告诉玩家新的数字范围而不是旧的范围。
当你想要为游戏增加随机性时,使用randint()函数。在许多游戏中,都会用到随机性(想想看,有那么多的桌上游戏要使用色子)。
3.5 欢迎玩家
在计算机给number分配了一个随机数之后,它和玩家打招呼:
10. print('Well, ' + myName + ', I am thinking of a number between 1 and 20.')
第10行的print()函数根据姓名来欢迎玩家,并且告诉他们计算机所考虑的数字范围。
乍一看,好像不止1个字符串参数,但是仔细看看这一行。加号把3个字符串连接成1个字符串。最终,只有1个字符串作为参数传递给了print()函数。如果再看一下,会看到引号中的逗号以及各个部分的字符串。
3.6 流程控制语句
在前面各章中,程序执行都是从程序中最上方的指令开始的,直接向下移动,依次执行每一条指令。但是,使用for、if、else和break语句,我们可以根据条件来执行循环或者跳过指令。这些语句就是流程控制语句(flow control statement),因为它们改变了程序执行过程中的流程。
3.6.1 使用循环来重复代码
第12行是一条for语句,它表示了一个for循环的开始:
12. for i in range(6):
循环可以一遍遍地重复执行代码。第12行会将这段代码重复6次。这条for语句以关键字for打头,后面跟着一个新的变量名、in关键字、对range()函数的一次调用,该函数指定了循环应该执行的次数,此外还有一个冒号。让我们来回顾一下一些循环的概念,以便你能够使用循环。
3.6.2 组织语句块
可以把许多代码行组织到一个语句块中。语句块中的每一行代码都拥有和语句块的第1行代码相同的、最小数量的缩进。可以通过查看代码行前面的空格数,来判断语句块的起始和结束。这就是代码行的缩进(indentation)。
Python程序员通常使用增加4个空格的缩进方式,来开始一个语句块。后续的也缩进4个空格的任何代码行,都是这个语句块的一部分。当有一行代码和该语句块开始之前的缩进相同,那么,这个语句块就结束了。语句块可以嵌套在其他已有的语句块之中。在图3-1所示的代码中,我们将语句块标记出来并进行编号。
图3-1 语句块和它们的缩进,语句块中的点表示空格
在图3-1中,第12行没有缩进,它不在任何语句块之中。第13行有4个空格的缩进。既然这行的缩进大于前一行的缩进,那么就开始了一个新的语句块。在这一行之后,拥有相同的缩进或更多的缩进的任何代码行,都被认为是语句块❶的一部分。如果Python遇到了比该语句块的第一行的缩进更少的一行,那么,这个语句块就结束了。空行可以忽略掉。
第18行有8个空格的缩进,这开始了一个新的语句块❷。这个语句块位于另一个语句块❶之中。但是第20行只有4个空格的缩进。因为缩进减少了,我们知道第18行的语句块❷结束了。并且由于第20行和第13行具有相同的缩进,我们知道这是在同一个语句块❶之中。
第21行再次将缩进增加到了8个空格,所以又开始了一个新的语句块,即语句块❸。在第23行,我们退出了语句块❸,并且在第24行,进入了一个语句块中的最后一个语句块,也就是语句块❹。在第24行,语句块❶和❹都结束了。
3.6.3 for循环语句
for语句表示一个循环的开始。循环可以重复执行相同的代码。当执行到一条for语句时,它会进入for语句之后的语句块。在运行完这个语句块中的所有代码之后,执行会回到该语句块的顶部,并再次运行所有的代码。
在交互式shell中输入如下的代码:
>>> for i in range(3):
print('Hello! i is set to', i)
Hello! i is set to 0
Hello! i is set to 1
Hello! i is set to 2
注意,在你输入了for i in range(3):并按下回车键之后,交互式shell并不会显示另一个>>>提示符,因为它期待你输入一个代码块。在最后一条指令之后,再次按下回车键,告诉交互式shell已经输入了一个代码块(只有在交互式shell中工作的时候,这才适用。当在文件编辑器中编写.py文件的时候,不需要插入一个空行)。
让我们来看一下guess.py的第12行的for循环:
12. for i in range(6):
13. print('Take a guess.') # Four spaces in front of "print"
14.guess = input()
15.guess = int(guess)
16.17.if guess < number:
18. print('Your guess is too low.') # Eight spaces in front of "print"
19.20.if guess > number:
21. print('Your guess is too high.')
22.23.if guess == number:
24. break25.26. if guess == number:
在猜数字游戏中,for语句块从第12行的for语句开始,并且这个for语句块之后的第1行是第26行。在for语句中,在条件之后总是有一个冒号(:)。以一个冒号结束的语句,期待在下一行开始一个新的语句块。图3-2说明了这一点。
图3-2 for循环的执行流程
图3-2展示了执行流程。执行将会在第13行进入一个for语句块,并且继续向下执行,一旦程序到达了for语句块的末尾,执行会回到第13行for语句块的开始处,而不是继续向下执行下一行代码。由于for语句中的range(6)函数调用,程序执行会这么做6次。通过循环的每一次执行,叫做一次迭代(iteration)。
可以把for语句理解为:“将如下的语句块中的代码执行一定的次数”。
3.7 玩家的猜测
第13行和第14行要求玩家去猜这个神秘的数字是几,并且让他们输入自己的猜测。
13.print('Take a guess.') # Four spaces in front of "print"
14.guess = input()
输入的这个数字会存储到一个名为guess的变量中。
3.8 使用int()函数、float()函数、str()函数和bool()函数来转换值
第15行调用了一个名为int()的新函数。
15. guess = int(guess)
int()函数接受一个参数,并且返回该参数的整数形式。
在交互式shell中输入如下语句,看看int()函数是如何工作的:
>>> int('42')42
int('42')调用将会返回整数值42。
>>> 3 + int('2')5
3 + int('2')这一行给出了一个表达式,使用int()函数的返回值作为该表达式一部分。其结果是整数值5:
尽管可以传递一个字符串给int()函数,但是不能传递任意的字符串。传递'forty-two'给int()函数将会导致一个错误。
>>> int('forty-two')Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
int('forty-two')ValueError: invalid literal for int() with base 10: 'forty-two'
传递给int()的字符串,必须是由数字组成的。在猜数字游戏中,我们使用input()函数来获取玩家的数字。
请记住,input()函数总是返回玩家所输入的文本的一个字符串。如果玩家输入的是5,input()函数将返回字符串'5',而不是整数5。Python不能使用比较操作符<和>来比较一个字符串和一个整数值:
>>> 4 < '5'Traceback (most recent call last):
File "<pyshell#0>", line 1, in <module>
4 < '5'TypeError: unorderable types: int() < str()
因此,我们需要将字符串转换为一个整数:
14. guess = input()15. guess = int(guess)
在第14行中,guess变量最初存储的是玩家输入的字符串值。第15行使用int()返回的整数值覆盖了guess中的字符串值。这就使得程序后面的代码能够比较guess是大于、小于或者等于number变量中的神秘数字。
类似的,float()和 str()函数分别返回和所传递的参数对应的浮点数和字符串的版本。尝试在交互式shell中输入如下内容:
>>> float('42')42.0>>> float(42)42.0
当把字符串'42'或整数42传递给float()的时候,就会返回浮点数42.0。
>>> str(42)'42'>>> str(42.0)'42.0'
当把整数42传递给str()的时候,它就会返回字符串'42'。但是,当把浮点数42.0传递给str()的时候,它就会返回字符串'42.0'。
使用int()、float()、str()和bool()函数,可以接受一种数据类型的值,而返回另一种数据类型的值。
3.9 布尔数据类型
Python中的每一个值,都属于一种数据类型。目前为止介绍的数据类型有整数、浮点数、字符串和布尔类型。布尔数据类型只有两个值:True或者False。这两个值的首字母必须大写,值的剩余部分必须小写。
例如,尝试把布尔值存储到变量中:
>>> spam = True>>> eggs = False
在这个示例中,我们将spam设置为True,把egg设置为False。记住要将首字母大写。
我们将用布尔值和比较操作符来组成条件。稍后,我们会先介绍比较操作符,然后介绍条件。
3.9.1 比较操作符
比较操作符比较两个值,并且会得到一个True或者False的布尔值。所有比较操作符如表3-1所示。
表3-1 比较操作符
操作符 | 操作符名称 |
---|---|
< | 小于 |
> | 大于 |
<= | 小于等于 |
>= | 大于等于 |
== | 等于 |
!= | 不等于 |
我们已经介绍过数学操作符+、−、*和/。和其他操作符一样,比较操作符把值组合成诸如guessesTaken < 6这样的表达式。
猜数字程序的第17行使用了小于比较操作符。
17. if guess < number:
稍后我们将更加详细地介绍if语句;现在,让我们来看看跟在if关键字后面的表达式(也就是guess < number部分)。这个表达式包含了两个值(即变量guess和变量number中的值),并且用一个操作符(即小于号 <)将它们连接了起来。
3.9.2 用条件检查True或False
条件(condition)是用比较操作符(如<或>)把两个值组合起来的一个表达式,并且条件的结果是一个布尔值。条件只是结果为True或False的表达式的另一种叫法而已。使用条件的一个位置,就是在if语句中。
例如,条件guessesTaken < 6表示“存储在guessesTaken中的值是否小于数字6?”。如果是,那么该条件结果为True。如果不是,该条件结果为False。
假设guess保存了整数10,而number保存了整数16。由于10小于16,这个条件计算为布尔值True。计算过程如下所示:
3.9.3 体验布尔值、比较操作符和条件
在交互式shell中,输入如下表达式来查看它们的布尔值结果:
>>> 0 < 6True>>> 6 < 0False
因为数字0小于数字6,所以条件0 < 6会返回布尔值True。但是因为6不小于0,所以条件6 < 0的结果是False。
请注意,10 < 10的结果为False,因为数字10并不小于数字10,它们一样大。
>>> 10 < 10False
如果Alice和Bob一样高,你不能说Alice比Bob高或者Alice比Bob矮,这两种说法都不对。
现在尝试在交互式shell中输入如下表达式:
>>> 10 == 10True>>> 10 == 11False>>> 11 == 10False>>> 10 != 10False
在这个示例中,10等于10,因此10 == 10计算为True。但是10不等于11,因此10 == 11为False。即便将两个值的顺序交换,11还是不等于10,因此11 == 10也为False。最后,10等于10,因此10 != 10为False。
我们也可以使用比较操作符来计算字符串表达式:
>>> 'Hello' == 'Hello'True>>> 'Goodbye' != 'Hello'True>>> 'Hello' == 'HELLO'False
'Hello'等于'Hello',因此'Hello' == 'Hello'为True。'Goodbye'不等于'Hello',因此'Goodbye' != 'Hello'也为True。注意,最后一行代码计算为False。在Python中,大写字母和小写字母是不同的,因此'Hello'不等于'HELLO'。
字符串值和整数值不会彼此相等。例如,在交互式shell中输入如下内容:
>>> 42 == 'Hello'False>>> 42 != '42'True
在第1个示例中,42是一个整数,而'Hello'是一个字符串,因此,这两个值并不相等,该表达式计算为False。在第2个示例中,字符串'42'仍然不是一个整数,因此,表达式“整数42不等于字符串'42'”计算为True。
3.9.4 =和==的区别
不要把赋值操作符(=)和“等于”比较操作符(==)搞混淆了。等号(=)用于赋值语句,用来把值存储到变量中;而双等号(==)用于表达式,用来判断两个值是否相等。当你想要使用其中某一个操作符时,很容易会错误地使用了另一个操作符。
只要记住,“等于”比较操作符(==)有两个字符,就像“不等于”比较操作符(!=)也有两个字符一样。
3.10 if语句
第17行是一条if语句。
17. if guess < number:18. print('Your guess is too low.') # Eight spaces in front of "print"
如果if语句的条件计算为True,if语句块后面的代码块将会运行。如果该条件为False,那么执行将会跳过if语句块中的代码。使用if语句,可以让程序只运行我们想要让它运行的某些代码。
第17行判断玩家的猜测是否小于计算机的神秘数字。如果是的话,那么执行会移入到第18行的if语句块,并且打印出一条消息来告诉玩家这一点。第20行判断玩家的猜测是否大于计算机的神秘数字。
20. if guess > number:21. print('Your guess is too high.')
如果这个条件为True,那么print()函数调用会告诉玩家,他们猜测的数字太大了。
3.11 用break语句提早离开循环
第23行的if语句判断guess是否等于神秘数字。如果是相等的,程序运行第24行的break语句。
23. if guess == number:24. break
break语句告诉执行要立即跳出for语句块,跳到for语句块结束之后的第一行。break语句只会出现在循环中,如一个for语句块中。
3.12 判断玩家是否赢了
第26行没有缩进,这表示for语句块已经结束了。
26. if guess == number:
执行会离开for语句块,要么由于它已经循环了6次(当玩家用尽了猜测的机会的时候),要么由于执行了第24行的break语句(当玩家猜对了数字的时候)。第26行判断玩家是否猜对了。如果猜对了,执行进入第27行的if语句块。
27. guessesTaken = str(guessesTaken)28. print('Good job, ' + myName + '! You guessed my number in ' +
guessesTaken + ' guesses!')
只有第26行的if语句中的条件为True的时候(也就是玩家猜对了计算机的数字),才会执行第27行和第28行。
第27行调用str()函数,该函数会返回guessesTaken的字符串形式。第28行把字符串连接到一起,告诉玩家他们赢得了游戏以及他们猜了多少次。只有字符串值才可以和其他的字符串连接。这就是为什么第27行必须把guessesTaken转换为字符串形式。否则的话,试图把一个字符串和一个整数连接在一起,将会导致Python显示一个错误。
3.13 判断玩家是否输了
如果玩家用尽了猜测次数,执行将会跳到如下的这行代码:
30. if guess != number:
第30行使用“不等于”比较操作符!=来判断玩家最后猜测的数字是否不等于神秘数字。如果这个条件结果为True,执行会移入到第31行的if语句块中。
第31和第32行在if语句块中,只有第30行的条件为True的时候,才会执行这两行。
31. number = str(number)32. print('Nope. The number I was thinking of was ' + number + '.')
在这个语句块中,程序告诉玩家他们没有猜中的神秘数字是什么。这需要把字符串连接起来,但是number中存储的是一个整数值。第31行会用字符串形式覆盖number,以便能够在第32行把它与字符串'Nope. The number I was thinking of was '连接起来。
此时,执行已经到了代码的末尾,程序结束了。恭喜!你已经编写了自己的第一款真正的游戏!
你可以通过修改玩家猜测的次数来让程序变得更难。要让玩家只能猜4次,把第12行的代码:
12. for i in range(4):
通过设置条件guessesTaken < 4,就可以确保循环中的代码只运行4次,而不是6次。这会使游戏变得更难。要让游戏简单一些,把条件设置为guessesTaken < 8或guessesTaken < 10。这会导致循环运行的次数更多一些,从而接受玩家更多次的猜测。
3.14 小结
编程就是为程序编写代码的行为,也就是说,创建计算机可以执行的程序。
当你看到某人使用计算机程序时(例如,玩你的“猜数字”游戏),你所看到的只是屏幕上出现的一些文本。根据程序的指令以及玩家用键盘输入的文本(程序的输入),程序决定了到底在屏幕上显示什么样的文本(程序输出)。程序只是基于用户输入而执行的动作的一个指令集。
只有几个不同种类的指令。
表达式是由操作符连接的值。表达式的最终结果是一个单独的值,例如2 + 2的结果是4,或者'Hello' + ' ' + 'World'的结果是'Hello World'。当表达式跟在if和while关键字后面的时候,我们也可以把它们称为条件。
赋值语句把值存储到变量中,以便在随后的程序中能够使用这个值。
if、while和break语句都是流程控制语句,可以导致执行跳过指令、循环地执行指令或者跳出循环。函数调用也可以通过跳转到函数中的指令来改变执行的 流程。
print()函数和input()函数。这两个函数分别在屏幕上显示文本和从键盘上接收到的文本。这叫做I/O(输入/输出),因为I/O负责程序的输入和输出。
就这些了,只有这4种指令。当然,关于这4种类型的指令,还有许多的细节。在本书后面的各章中,我们将学习新的数据类型和操作符、新的流程控制语句以及许多其他的Python函数;还有不同类型的I/O,如用鼠标输入、输出声音和图形,而不是只输出文本。
本文摘自《Python游戏编程快速上手(第4版)》
延伸推荐:
点击阅读原文,购买《Python游戏编程快速上手(第4版)》